﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

using UnityEditor;

using UnityEngine;

using Object = UnityEngine.Object;

namespace ComponentAutoBind.Editor
{
    [CustomEditor(typeof(ComponentAutoBindTool))]
    public class ComponentAutoBindToolInspector : UnityEditor.Editor
    {
        #region 字段
        private ComponentAutoBindTool m_Target;

        private SerializedProperty m_BindComponents;

        private SerializedProperty m_RuleHelperTypeName;
        private SerializedProperty m_Namespace;
        private SerializedProperty m_ClassName;
        private SerializedProperty m_ScriptFolderPath;
        private SerializedProperty m_AuthorName;

        public List<BindData> m_BindDatas = new List<BindData>();
        public List<BindData> m_TempBindDatas = new List<BindData>();
        private readonly List<string> m_TempFieldNames = new List<string>();
        private readonly List<string> m_TempComponentTypeNames = new List<string>();

        private string[] m_RuleHelperTypeNames;
        private int m_RuleHelperTypeNameIndex;
        private AutoBindRuleHelperBase m_RuleHelper;

        private bool m_HasError;
        private bool m_HasInvalidComponent;

        private int m_AuthorIndex;
        private string m_AuthorNiceName;
        private string[] m_AuthorNiceNames;
        private Dictionary<string, Author> m_Authors;

        private AutoBindGlobalSetting m_Setting;
        #endregion

        private void OnEnable()
        {
            m_HasError = false;

            // 加载全局配置
            string[] paths = AssetDatabase.FindAssets("t:AutoBindGlobalSetting");

            if (paths.Length == 0)
            {
                m_HasError = true;
                Debug.LogError("未找到组件自动绑定全局规则设置: AutoBindGlobalSetting");

                return;
            }

            if (paths.Length > 1)
            {
                m_HasError = true;
                Debug.LogError("存在多份组件自动绑定全局规则配置文件");

                return;
            }

            string path = AssetDatabase.GUIDToAssetPath(paths[0]);
            m_Setting = AssetDatabase.LoadAssetAtPath<AutoBindGlobalSetting>(path);

            // 加载开发者名单
            m_Authors = new Dictionary<string, Author>();

            foreach (Author author in m_Setting.Authors)
            {
                m_Authors.Add(author.NiceName, author);
            }

            m_AuthorNiceNames = m_Setting.Authors.Select(author => author.NiceName).ToArray();

            m_Target = (ComponentAutoBindTool)target;

            // 初始化规则辅助器
            m_RuleHelperTypeNames =
                TypeHelper.GetTypeNames(typeof(AutoBindRuleHelperBase), m_Setting.HelperAssemblyNames.ToArray());
            m_RuleHelperTypeName = serializedObject.FindProperty("m_RuleHelperTypeName");

            if (!InitRuleHelper())
            {
                m_HasError = true;

                return;
            }

            m_HasInvalidComponent = false;
            m_BindComponents = serializedObject.FindProperty("m_BindComponents");
            GenerateBindDatas();

            m_Namespace = serializedObject.FindProperty("m_Namespace");
            m_ClassName = serializedObject.FindProperty("m_ClassName");
            m_ScriptFolderPath = serializedObject.FindProperty("m_ScriptFolderPath");
            m_AuthorName = serializedObject.FindProperty("m_AuthorName");
            m_AuthorIndex = -1;

            if (!string.IsNullOrEmpty(m_AuthorName.stringValue))
            {
                for (int i = 0; i < m_Setting.Authors.Count; i++)
                {
                    Author author = m_Setting.Authors[i];

                    if (author.Name == m_AuthorName.stringValue)
                    {
                        m_AuthorIndex = i;
                        m_AuthorNiceName = m_AuthorNiceName = author.NiceName;

                        break;
                    }
                }
            }

            m_Namespace.stringValue = string.IsNullOrEmpty(m_Namespace.stringValue)
                ? m_Setting.Namespace
                : m_Namespace.stringValue;

            m_ClassName.stringValue = string.IsNullOrEmpty(m_ClassName.stringValue)
                ? m_Target.gameObject.name
                : m_ClassName.stringValue;

            m_ScriptFolderPath.stringValue = string.IsNullOrEmpty(m_ScriptFolderPath.stringValue)
                ? m_Setting.ScriptFolderPath
                : m_ScriptFolderPath.stringValue;

            serializedObject.ApplyModifiedProperties();
        }

        public override void OnInspectorGUI()
        {
            if (m_HasError)
            {
                return;
            }

            serializedObject.Update();

            DrawTopButton();

            DrawHelperSelect();

            DrawSetting();

            DrawKvData();

            serializedObject.ApplyModifiedProperties();
        }

        /// <summary>
        /// 初始化自动绑定规则辅助器。
        /// </summary>
        private bool InitRuleHelper()
        {
            if (m_RuleHelperTypeNames.Length <= 0)
            {
                Debug.LogError($"项目不存在组件自动绑定规则的辅助器");

                return false;
            }

            string ruleHelperTypeName = m_RuleHelperTypeName.stringValue;

            if (string.IsNullOrEmpty(ruleHelperTypeName))
            {
                m_RuleHelperTypeName.stringValue = ruleHelperTypeName = m_RuleHelperTypeNames[0];
            }

            if (!string.IsNullOrEmpty(ruleHelperTypeName) && !m_RuleHelperTypeNames.Contains(ruleHelperTypeName))
            {
                Debug.LogWarning($"当前界面使用的绑定规则辅助器[{ruleHelperTypeName}]不存在了, 使用默认的辅助器[{m_RuleHelperTypeNames[0]}]。");

                m_RuleHelperTypeName.stringValue = ruleHelperTypeName = m_RuleHelperTypeNames[0];
            }

            m_RuleHelper =
                (AutoBindRuleHelperBase)CreateHelperInstance(ruleHelperTypeName,
                    m_Setting.HelperAssemblyNames.ToArray());
            m_RuleHelper.InitAutoBindRule();

            if (m_RuleHelper == null)
            {
                Debug.LogError($"创建自动绑定规则的辅助器失败");

                return false;
            }

            for (int i = 0; i < m_RuleHelperTypeNames.Length; i++)
            {
                if (ruleHelperTypeName == m_RuleHelperTypeNames[i])
                {
                    m_RuleHelperTypeNameIndex = i;
                }
            }

            return true;
        }

        /// <summary>
        /// 生成绑定组件数据。
        /// </summary>
        private void GenerateBindDatas()
        {
            m_HasInvalidComponent = false;
            m_BindDatas.Clear();

            for (int i = 0; i < m_BindComponents.arraySize; i++)
            {
                SerializedProperty serializedProperty = m_BindComponents.GetArrayElementAtIndex(i);

                Component component = serializedProperty.objectReferenceValue as Component;

                if (component == null)
                {
                    m_HasInvalidComponent = true;

                    break;
                }

                string fieldName = m_RuleHelper.ParseFieldName(component);

                if (fieldName == null)
                {
                    m_HasInvalidComponent = true;

                    break;
                }

                m_BindDatas.Add(new BindData(fieldName, component));
            }
        }

        /// <summary>
        /// 内部校验是否有无效组件。
        /// </summary>
        /// <returns></returns>
        private bool CheckHasInvalidComponent()
        {
            if (!m_HasInvalidComponent)
            {
                return false;
            }

            EditorUtility.DisplayDialog("提示", "存在无效组件, 先删除空引用组件", "确定");

            return true;
        }

        /// <summary>
        /// 绘制顶部按钮。
        /// </summary>
        private void DrawTopButton()
        {
            EditorGUILayout.BeginHorizontal();

            if (GUILayout.Button("字段排序"))
            {
                Sort();
            }

            if (GUILayout.Button("全部删除"))
            {
                RemoveAll();
            }

            if (GUILayout.Button("删除空引用"))
            {
                RemoveNull();
            }

            if (GUILayout.Button("自动绑定组件"))
            {
                AutoBindComponent();
            }

            if (GUILayout.Button("生成绑定代码"))
            {
                AutoGenerateBindCode();
            }

            EditorGUILayout.EndHorizontal();
        }

        /// <summary>
        /// 字段排序。
        /// </summary>
        private void Sort()
        {
            if (CheckHasInvalidComponent())
            {
                return;
            }

            m_BindDatas.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
            SyncBindComponents();
        }

        /// <summary>
        /// 全部删除。
        /// </summary>
        private void RemoveAll()
        {
            m_BindDatas.Clear();

            SyncBindComponents();
        }

        /// <summary>
        /// 删除无效组件。
        /// </summary>
        private void RemoveNull()
        {
            for (int i = m_BindComponents.arraySize - 1; i >= 0; i--)
            {
                SerializedProperty serializedProperty = m_BindComponents.GetArrayElementAtIndex(i);

                // 删除空引用组件
                Component component = serializedProperty.objectReferenceValue as Component;

                if (component == null)
                {
                    m_BindComponents.DeleteArrayElementAtIndex(i);

                    continue;
                }

                // 删除命名不符合绑定规则组件 
                string fieldName = m_RuleHelper.ParseFieldName(component);

                if (fieldName == null)
                {
                    m_BindComponents.DeleteArrayElementAtIndex(i);
                }
            }

            GenerateBindDatas();
        }

        /// <summary>
        /// 自动绑定组件。
        /// </summary>
        private void AutoBindComponent()
        {
            m_TempBindDatas.Clear();

            bool hasInvalidComponent = false;
            Transform[] childs = m_Target.gameObject.GetComponentsInChildren<Transform>(true);

            foreach (Transform child in childs)
            {
                m_TempFieldNames.Clear();
                m_TempComponentTypeNames.Clear();

                // child节点上组件命名符合自动绑定规则，需要添加绑定数据。
                if (m_RuleHelper.TryParseBindComponent(child, m_TempFieldNames, m_TempComponentTypeNames))
                {
                    // 一个child节点允许绑定多个组件。
                    for (int i = 0; i < m_TempFieldNames.Count; i++)
                    {
                        Component component = child.GetComponent(m_TempComponentTypeNames[i]);

                        if (component == null)
                        {
                            hasInvalidComponent = true;

                            Debug.LogError(
                                $"[{child.name}]自动绑定失败， 命名中[{m_TempFieldNames[i]}]不存在对应的组件[{m_TempComponentTypeNames[i]}]。");

                            break;
                        }

                        m_TempBindDatas.Add(new BindData(m_TempFieldNames[i],
                            child.GetComponent(m_TempComponentTypeNames[i])));
                    }
                }
            }

            if (hasInvalidComponent)
            {
                // 预制体添加无效组件时，不影响原有已绑定组件正常运行。
                m_TempBindDatas.Clear();

                return;
            }

            m_BindDatas.Clear();
            m_BindDatas.AddRange(m_TempBindDatas);
            m_TempBindDatas.Clear();

            SyncBindComponents();
        }

        /// <summary>
        /// 绘制辅助器选择框
        /// </summary>
        private void DrawHelperSelect()
        {
            int selectedIndex =
                EditorGUILayout.Popup("绑定规则辅助器：", m_RuleHelperTypeNameIndex, m_RuleHelperTypeNames);

            if (selectedIndex == m_RuleHelperTypeNameIndex)
            {
                return;
            }

            m_RuleHelperTypeNameIndex = selectedIndex;
            m_RuleHelperTypeName.stringValue = m_RuleHelperTypeNames[selectedIndex];

            m_RuleHelper = (AutoBindRuleHelperBase)CreateHelperInstance(m_RuleHelperTypeNames[selectedIndex],
                m_Setting.HelperAssemblyNames.ToArray());
            m_RuleHelper.InitAutoBindRule();
        }

        /// <summary>
        /// 绘制设置项
        /// </summary>
        private void DrawSetting()
        {
            EditorGUILayout.BeginHorizontal();

            int selectedIndex =
                EditorGUILayout.Popup("开发人员：", m_AuthorIndex, m_AuthorNiceNames);

            if (selectedIndex != m_AuthorIndex)
            {
                m_AuthorIndex = selectedIndex;
                m_AuthorName.stringValue = m_Setting.Authors[selectedIndex].Name;

                m_AuthorNiceName = m_AuthorNiceNames[selectedIndex];
            }

            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            m_Namespace.stringValue = EditorGUILayout.TextField(new GUIContent("命名空间："), m_Namespace.stringValue);

            if (GUILayout.Button("使用默认值"))
            {
                m_Namespace.stringValue = m_Setting.Namespace;
            }

            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            m_ClassName.stringValue = EditorGUILayout.TextField(new GUIContent("类名："), m_ClassName.stringValue);

            if (GUILayout.Button("使用物体名"))
            {
                m_ClassName.stringValue = m_Target.gameObject.name;
            }

            EditorGUILayout.EndHorizontal();

            EditorGUILayout.LabelField("代码保存目录：");
            EditorGUILayout.LabelField(m_ScriptFolderPath.stringValue);
            EditorGUILayout.BeginHorizontal();

            if (GUILayout.Button("选择目录"))
            {
                string temp = m_ScriptFolderPath.stringValue;
                m_ScriptFolderPath.stringValue = EditorUtility.OpenFolderPanel("选择代码保存目录", Application.dataPath, "");

                if (string.IsNullOrEmpty(m_ScriptFolderPath.stringValue))
                {
                    m_ScriptFolderPath.stringValue = temp;
                }
            }

            if (GUILayout.Button("使用默认值"))
            {
                m_ScriptFolderPath.stringValue = m_Setting.ScriptFolderPath;
            }

            EditorGUILayout.EndHorizontal();
        }

        /// <summary>
        /// 绘制键值对数据
        /// </summary>
        private void DrawKvData()
        {
            //绘制key value数据
            int needDeleteIndex = -1;

            EditorGUILayout.BeginVertical();

            for (int i = 0; i < m_BindDatas.Count; i++)
            {
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField($"[{i}]", GUILayout.Width(25));
                EditorGUILayout.LabelField(m_BindDatas[i].Name, GUILayout.Width(150));
                EditorGUILayout.ObjectField(m_BindDatas[i].BindComponent, typeof(Component), true);

                if (GUILayout.Button("X"))
                {
                    //将元素下标添加进删除list
                    needDeleteIndex = i;
                }

                EditorGUILayout.EndHorizontal();
            }

            //删除data
            if (needDeleteIndex != -1)
            {
                m_BindDatas.RemoveAt(needDeleteIndex);
                SyncBindComponents();
            }

            EditorGUILayout.EndVertical();
        }

        /// <summary>
        /// 同步绑定数据到组件字段上。
        /// </summary>
        private void SyncBindComponents()
        {
            m_BindComponents.ClearArray();

            for (int i = 0; i < m_BindDatas.Count; i++)
            {
                m_BindComponents.InsertArrayElementAtIndex(i);
                m_BindComponents.GetArrayElementAtIndex(i).objectReferenceValue = m_BindDatas[i].BindComponent;
            }
        }

        /// <summary>
        /// 创建辅助器实例。
        /// </summary>
        private object CreateHelperInstance(string helperTypeName, string[] assemblyNames)
        {
            foreach (string assemblyName in assemblyNames)
            {
                Assembly assembly;

                try
                {
                    assembly = Assembly.Load(assemblyName);
                }
                catch
                {
                    continue;
                }

                if (assembly == null)
                {
                    continue;
                }

                object instance = assembly.CreateInstance(helperTypeName);

                if (instance != null)
                {
                    return instance;
                }
            }

            return null;
        }

        /// <summary>
        /// 生成自动绑定代码。
        /// </summary>
        private void AutoGenerateBindCode()
        {
            if (CheckHasInvalidComponent())
            {
                return;
            }

            GameObject go = m_Target.gameObject;

            string className = !string.IsNullOrEmpty(m_ClassName.stringValue) ? m_ClassName.stringValue : go.name;

            if (string.IsNullOrEmpty(className))
            {
                EditorUtility.DisplayDialog("提示", "类名为空，输入类名或使用节点名字（点击按钮 [物体名]）", "确定");

                return;
            }

            if (className.Equals("UIForm"))
            {
                EditorUtility.DisplayDialog("提示", $"类名无效，不要使用`{className}`作为类名", "确定");

                return;
            }

            if (string.IsNullOrEmpty(m_AuthorNiceName))
            {
                EditorUtility.DisplayDialog("提示", "未选择开者发人员", "确定");
            }

            string scriptFolderPath = !string.IsNullOrEmpty(m_ScriptFolderPath.stringValue)
                ? m_ScriptFolderPath.stringValue
                : m_Setting.ScriptFolderPath;

            if (!Directory.Exists(scriptFolderPath))
            {
                Debug.LogError($"[{go.name}]的代码保存目录[{scriptFolderPath}]无效。");

                return;
            }

            string scriptPath = $"{scriptFolderPath}/{className}.BindComponents.cs";

            using (StreamWriter streamWriter = new StreamWriter(scriptPath))
            {
                // 写入文件头
                WriteHeader(streamWriter);

                // 写入使用的命名空间
                WriteUsingNamespace(streamWriter);

                // 缩进层级
                int indentLevel = 0;
                string indent = "";

                if (!string.IsNullOrEmpty(m_Namespace.stringValue))
                {
                    //类所在命名空间
                    streamWriter.Write($@"

namespace {m_Namespace.stringValue} 
{{");
                    indentLevel = 1;
                    indent = new string('\t', indentLevel);
                }

                // 写入类名
                streamWriter.Write($@"
{indent}// <auto-generated>
{indent}// 当前脚d>本为自动生成的代码, 由ComponentAutoBind生成。
{indent}// 对脚本的修改会被覆盖在重新生成脚本时, 请勿手动修改。
{indent}// </auto-generate>
{indent}public partial class {className}
{indent}{{");
                indentLevel += 1;
                indent = new string('\t', indentLevel);

                streamWriter.Write($@"
{indent}#region 组件字段");

                // 写入定义所有绑定组件字段
                foreach (BindData bindData in m_BindDatas)
                {
                    streamWriter.Write($@"
{indent}private {bindData.BindComponent.GetType().Name} m_{bindData.Name};");
                }

                streamWriter.Write($@"
{indent}#endregion");
                streamWriter.WriteLine();

                // 写入获取绑定组件函数
                streamWriter.Write($@"
{indent}///<summary>
{indent}/// 获取所有绑定组件。
{indent}///</summary>
{indent}/// <param name=""go"">组件对象。</param>
{indent}private void GetBindComponents(GameObject go)
{indent}{{");

                indentLevel += 1;
                indent = new string('\t', indentLevel);

                streamWriter.Write($@"
{indent}ComponentAutoBindTool tool = go.GetComponent<ComponentAutoBindTool>();");

                streamWriter.WriteLine();

                // 写入获取绑定组件的代码, 根据索引进行获取
                for (int i = 0; i < m_BindDatas.Count; i++)
                {
                    BindData bindData = m_BindDatas[i];
                    string fieldName = $"m_{bindData.Name}";

                    streamWriter.Write($@"
{indent}{fieldName} = tool.GetBindComponent<{bindData.BindComponent.GetType().Name}>({i});");
                }

                indentLevel -= 1;
                indent = new string('\t', indentLevel);

                // 函数结束
                streamWriter.Write($@"
{indent}}}");
                indentLevel -= 1;
                indent = new string('\t', indentLevel);

                // 类结束
                streamWriter.Write($@"
{indent}}}");
                indentLevel -= 1;
                indent = new string('\t', indentLevel);

                // 命名空间结束
                if (!string.IsNullOrEmpty(m_Namespace.stringValue))
                {
                    streamWriter.Write($@"
{indent}}}");
                }
            }

            EOLNormalize(scriptPath);

            AssetDatabase.Refresh();

            Debug.Log($"UI绑定代码自动生成完毕");
            Object asset = AssetDatabase.LoadAssetAtPath<Object>(scriptPath);

            if (asset != null)
            {
                EditorGUIUtility.PingObject(asset);
            }
        }

        /// <summary>
        /// 写入头部信息代码。
        /// </summary>
        /// <param name="streamWriter"></param>
        private void WriteHeader(StreamWriter streamWriter)
        {
            Author author = m_Authors[m_AuthorNiceName];

            streamWriter.Write($@"// ------------------------------------------------------------
// {m_Setting.ScriptHeader}
// Copyright © 2025-2030 {m_Setting.CompanyName} All rights reserved.
// Author: {author.Name}
// Homepage: {author.Homepage}
// Feedback email: {author.Email}
// ------------------------------------------------------------");
        }

        /// <summary>
        /// 写入引用命名空间代码。
        /// </summary>
        /// <param name="streamWriter"></param>
        private void WriteUsingNamespace(StreamWriter streamWriter)
        {
            streamWriter.Write(@"

using UnityEngine;
using UnityEngine.UI;

using ComponentAutoBind;");
        }

        /// <summary>
        /// 换行符标准化, 不同操作系统换行符不一致, 统一为windows下CRLF
        /// </summary>
        /// <param name="filePath"></param>
        private void EOLNormalize(string filePath)
        {
            string text;
            string normalizeText;

            using (StreamReader streamReader = new StreamReader(filePath))
            {
                text = streamReader.ReadToEnd();
                // 先统一将\r\n换成linux/macOS样式 \n, 再统一转换成windows样式 \r\n
                normalizeText = text.Replace("\r\n", "\n").Replace("\n", "\r\n");
            }

            if (text != normalizeText)
            {
                File.WriteAllText(filePath, normalizeText, Encoding.UTF8);
            }
        }
    }
}